﻿using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Mozhi.Abp.OperationLog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.Uow;

namespace Mozhi.Abp.EntityFrameworkCore
{
    public abstract class UserLogingDbContext<TDbContext> : AbpDbContext<TDbContext> where TDbContext : DbContext
    {
        private IOperationLogger _operationLogger;

        protected UserLogingDbContext(DbContextOptions<TDbContext> options , IOperationLogger operationLogger) : base(options)
        {
            _operationLogger = operationLogger;
        }

        public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
        {
            try
            {
                List<OperationLogRecordEvent> operationLogRecordEvents = GetOperationLogRecordEvents();

                var result = await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);

                PublishOperationLogEvents(operationLogRecordEvents);

                return result;
            }
            catch (DbUpdateConcurrencyException ex)
            {
                throw new AbpDbConcurrencyException(ex.Message, ex);
            }
            finally
            {
                ChangeTracker.AutoDetectChangesEnabled = true;
            }
        }

        #region
        private void PublishOperationLogEvents(List<OperationLogRecordEvent> events)
        {
            if (events == null || events.Count == 0)
                return;

            int order = 0;

            foreach (var @event in events)
            {
                UnitOfWorkManager.Current?.AddOrReplaceDistributedEvent(new UnitOfWorkEventRecord(@event.GetType(), @event, order++));
            }
        }

        private List<OperationLogRecordEvent> GetOperationLogRecordEvents()
        {
            var events = new List<OperationLogRecordEvent>();
            foreach (var entry in ChangeTracker.Entries().ToList())
            {
                if (entry.State == EntityState.Detached ||
                    entry.State == EntityState.Unchanged)
                    continue;

                var entityName = entry.Entity.GetType().Name;

                OperationLogRecordEvent @event;
                switch (entry.State)
                {
                    case EntityState.Added:
                        @event = _operationLogger.GetRecordEvent(entityName, OperationType.Create, entry.Entity, null);
                        break;
                    case EntityState.Deleted:
                        @event = _operationLogger.GetRecordEvent(entityName, OperationType.Delete, entry.Entity, null);
                        break;
                    case EntityState.Modified:
                        @event = _operationLogger.GetRecordEvent(entityName, OperationType.Update, entry.OriginalValues.ToObject(), entry.CurrentValues.ToObject());
                        break;
                    default:
                        @event = null;
                        break;
                }
                if (@event != null)
                    events.Add(@event);
            }
            return events;
        }
        #endregion
    }
}
